package com.opennews.openplatform

import com.opennews.openplatform.constants.RoleConstant
import com.opennews.openplatform.security.RequestMapManager
import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import org.springframework.http.HttpMethod

@Transactional
@GrailsCompileStatic
class RequestMapService {
    /**
     * Prepares the initial data for Role.
     */
    void init() {
        if (RequestMap.count() == 0) {
            // region: The configuration allows anonymous access.
            // Allows to login.
            new RequestMap().with {
                configAttribute = RequestMapManager.PERMIT_ALL
                url = "/appErrorReport/**"
                save()
            }

            // Allows to register and etc.
            new RequestMap().with {
                configAttribute = RequestMapManager.PERMIT_ALL
                url = "/authentication/**"
                save()
            }
            // endregion

            // region: The configuration allows authenticated access.
            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                url = "/mapLocation/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.POST
                url = "/authentication/unregister"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/appReleaseLog/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/accountGroup/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/accountGroupUser/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.POST
                url = "/accountGroupUser/quit"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/bank/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/bankCard/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/dataInitialization/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                url = "/file/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/incomeExpenseAnalysis/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/incomeExpenseDetail/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/incomeExpenseDetailType/queryByAccountGroup"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/incomeExpenseDetailType/queryAnalysisNotIncludedDetailTypes"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                url = "/joinAccountGroupRequest/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/journey/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/journeyIncomeExpenseDetail/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/region/**"
                save()
            }

            // This is used by upload package.
            // We need to check and refresh the token if it can.
            // Remember to set this url as isFullyAuthenticated() in spring security rest.
            // We are making a http get request to this url before upload.
            // It's the easy way to do the auto-refresh for upload.
            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                httpMethod = HttpMethod.GET
                url = "/tokenCheck/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                url = "/user/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.IS_AUTHENTICATED
                url = "/userActiveToken/**"
                save()
            }
            // endregion

            // region: The configuration only allows account group user and admin access.
            new RequestMap().with {
                httpMethod = HttpMethod.POST
                configAttribute = RequestMapManager.hasAnyRole([RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN, RoleConstant.ROLE_ACCOUNT_GROUP_USER])
                url = "/incomeExpenseDetail/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasAnyRole([RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN, RoleConstant.ROLE_ACCOUNT_GROUP_USER])
                httpMethod = HttpMethod.POST
                url = "/journey/**"
                save()
            }

            new RequestMap().with {
                httpMethod = HttpMethod.POST
                configAttribute = RequestMapManager.hasAnyRole([RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN, RoleConstant.ROLE_ACCOUNT_GROUP_USER])
                url = "/journeyIncomeExpenseDetail/**"
                save()
            }
            // endregion

            // region: The configuration allows account group admin access.
            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/accountGroup/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/accountGroupUser/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/bankCard/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/file/uploadAvatar"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.GET
                url = "/incomeExpenseDetailType/checkExisting"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/incomeExpenseDetailType/**"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/joinAccountGroupRequest/send"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.GET
                url = "/role/queryNonSystemLevelRoles"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.GET
                url = "/user/queryUsersNotInAccountGroup"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/user/add"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/user/updatePassword"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/user/updateEnabled"
                save()
            }

            new RequestMap().with {
                configAttribute = RequestMapManager.hasRole(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)
                httpMethod = HttpMethod.POST
                url = "/user/delete"
                save()
            }
            // endregion
        }
    }

    /**
     * Queries and caches config request map from data source.
     */
    void refreshCache() {
        RequestMapManager.reloadCachedRequestMaps()
    }

    /**
     * Extracts role names from request map config attribute.
     * Example: hasRole('admin'), hasAnyRole('reporter', 'planner').
     * @param configAttribute: Property configAttribute of request map.
     * @return String list of role name.
     */
    List<String> extractRoleNames(String configAttribute) {
        List<String> roleNames = []

        // This would match the role names, like 'admin' or 'reporter', 'planner'.
        def match = configAttribute =~ /(?:${RequestMapManager.HAS_ROLE_PREFIX}|${RequestMapManager.HAS_ANY_ROLE_PREFIX}|${RequestMapManager.HAS_NO_ROLE_PREFIX}|${RequestMapManager.HAS_NO_ANY_ROLE_PREFIX})\((.+)\)/

        // If match found.
        if (match.hasGroup()) {
            def matchedStrings = match[0] as List<String>

            if (matchedStrings.size() == 2) {
                // Removes all single quotes.
                def matchedRoles = matchedStrings[1].replaceAll("'", "")

                // Removes all double quotes.
                matchedRoles = matchedRoles.replaceAll("\"", "")

                // Splits the string by comma.
                def splitedRoles = matchedRoles.split(",")

                // Trims space for each role name.
                splitedRoles.each {
                    roleNames << it.trim()
                }
            }
        }

        return roleNames
    }

    /**
     * Checks if security request map configAttribute contains any role in roleNames.
     * @param roleNames: String list of role names.
     * @param configAttribute: String of config attribute. Example: hasAnyRole('ROLE_ACCOUNT_GROUP_USER','ROLE_ACCOUNT_GROUP_ADMIN')
     * @return
     */
    boolean containsAnyRoleInConfigAttribute(List<String> roleNames, String configAttribute) {
        return containsAnyRoleInConfigRoleNames(roleNames, extractRoleNames(configAttribute))
    }

    /**
     * Checks if configuredRoleNames contains any role in roleNames.
     * @param roleNames: String list of role names.
     * @param configRoleNames: String list of configured role names.
     * @return True means condition meets. False means NOT.
     */
    private boolean containsAnyRoleInConfigRoleNames(List<String> roleNames, List<String> configRoleNames) {
        def containsAnyRole = false

        for (String roleName in roleNames) {
            for (String configRoleName in configRoleNames) {
                if (roleName.toLowerCase() == configRoleName.toLowerCase()) {
                    containsAnyRole = true
                    break
                }
            }
        }

        return containsAnyRole
    }
}